package ffmpeg

import (
	"bytes"
	"context"
	"fmt"
	"github.com/spf13/cast"
	"image"
	"io"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

type Exec struct {
	ffmpegFile string
	Loglevel   Loglevel
}

type Option func(*Exec)

func WithLoglevel(loglevel Loglevel) Option {
	return func(e *Exec) {
		e.Loglevel = loglevel
	}
}

func New(ffmpegFile string, options ...Option) *Exec {
	abs, _ := filepath.Abs(ffmpegFile)
	e := &Exec{
		ffmpegFile: abs,
		Loglevel:   LogLevelInfo,
	}
	for _, option := range options {
		option(e)
	}
	return e
}

func (e *Exec) ExecCtx(ctx context.Context, args ...string) error {
	_, err := e.ExecCtxValue(ctx, e.Loglevel, args...)
	return err
}

func (e *Exec) ExecCtxValue(ctx context.Context, loglevel Loglevel, args ...string) (string, error) {
	args = append(args, "-loglevel", cast.ToString(int(loglevel)))
	cmd := exec.CommandContext(ctx, e.ffmpegFile, args...)
	errContext := new(bytes.Buffer)
	cmd.Stderr = io.MultiWriter(errContext, os.Stderr)
	cmd.Stdout = io.MultiWriter(errContext, os.Stdout)
	err := cmd.Run()
	if err != nil {
		err = fmt.Errorf("cmd: [ %s ]\n%s\n\n%s", cmd.String(), errContext.String(), err.Error())
	}
	return errContext.String(), err
}

// VideoFrameExtraction 将视频以每秒24帧的方式提取
// videoFile 视频文件
// outputDir 输出目录
func (e *Exec) VideoFrameExtraction(ctx context.Context, videoFile, outputDir string) error {
	args := []string{
		"-i", videoFile,
		"-vf", "fps=24",
		"-f", "image2",
		"-y",
		filepath.Join(outputDir, "%013d.png"),
	}
	return e.ExecCtx(ctx, args...)
}

// MergeImg2Video 合并图片成视频
func (e *Exec) MergeImg2Video(ctx context.Context, imgDir, audioFile, outputFile string) error {
	args := []string{
		"-y",
		"-r", "24",
		"-i", filepath.Join(imgDir, "%013d.png"),
		"-i", audioFile,
		"-vcodec", "libx264",
		"-c:a", "aac",
		"-crf", "20",
		"-preset", "slow",
		"-movflags", "faststart",
		"-vf", "format=yuv420p",
		outputFile,
	}
	return e.ExecCtx(ctx, args...)
}

// Overlay 视频叠加
func (e *Exec) Overlay(ctx context.Context, isVideo bool, outputFile string, elementFile ...string) error {
	var args []string
	var filterComplexOverlay string
	for index, item := range elementFile {
		args = append(args, "-i", item)
		filterComplexOverlay += fmt.Sprintf("[%d:v]", index)
	}
	args = append(args,
		"-filter_complex", filterComplexOverlay+"overlay",
		"-crf", "18",
	)
	if isVideo {
		args = append(args,
			"-codec:a", "copy",
			"-c:v", "libx264",
		)
	}
	args = append(args, "-y", outputFile)
	return e.ExecCtx(ctx, args...)
}

// GenerateVideoPreviewImg 视频预览图
func (e *Exec) GenerateVideoPreviewImg(ctx context.Context, videoFilePath, outputFilePath string) error {
	args := []string{
		"-i", videoFilePath,
		"-vf", "select=eq(n\\,0)",
		"-vframes", "1",
		outputFilePath,
	}
	return e.ExecCtx(ctx, args...)
}

// Scale 缩放
func (e *Exec) Scale(ctx context.Context, input string, w, h int, output string) error {
	args := []string{
		"-i", input,
		"-vf", fmt.Sprintf("scale=%d:%d", w, h),
		output,
	}
	return e.ExecCtx(ctx, args...)
}

// GetResolution 获取分辨率
func (e *Exec) GetResolution(ctx context.Context, input string) (image.Point, error) {
	var p image.Point
	// 这里绝对是会出错的, 就算获取到了分辨率, 这里不判断错误, 让后面获取不到实际分辨率再将这里的错误返回出去
	info, err := e.ExecCtxValue(ctx, LogLevelInfo, "-i", input)
	var resolutionLine string
	for _, line := range strings.Split(info, "\n") {
		if strings.Contains(line, "Stream #0:0") && strings.Contains(line, "Video:") {
			resolutionLine = line
			break
		}
	}
	re := regexp.MustCompile(`\w[1-9]\d*x[1-9]\d+\w`)
	match := re.FindStringSubmatch(resolutionLine)
	if len(match) > 0 {
		splits := strings.Split(match[0], "x")
		if len(splits) > 1 {
			p.X = cast.ToInt(splits[0])
			p.Y = cast.ToInt(splits[1])
		}
	}
	if p.X == 0 || p.Y == 0 {
		return p, err
	}
	return p, nil
}
